﻿using System;
using System.Net;
using System.Diagnostics;

// From C:\Program Files (x86)\Microsoft Visual Studio 10.0\Common7\IDE\ReferenceAssemblies\v2.0\
using Microsoft.TeamFoundation.Client;
using Microsoft.TeamFoundation.Common;
using Microsoft.TeamFoundation.WorkItemTracking.Client;
using Microsoft.TeamFoundation.VersionControl.Client;

// From C:\Program Files\Microsoft Team Foundation Server 2010\Tools\
using Microsoft.TeamFoundation.Framework.Server;
using Microsoft.TeamFoundation.VersionControl.Server;
using Changeset = Microsoft.TeamFoundation.VersionControl.Server.Changeset;
using Microsoft.TeamFoundation.Framework.Client;
using Microsoft.TeamFoundation.Framework.Common;

// Project reference to Common
using Microsoft.DPE.UK.TFS.EmbeddedWorkItems.Common;

namespace Microsoft.DPE.UK.TFS.EmbeddedWorkItems
{
	/// <summary>
	/// This EventHandler will be invoked at check-in and will look for any embedded work item ids in check-in comments and add those workitems into the check-in.
	/// 
	/// Design rationale: I couldn't get access to the Changeset from the client and therefore this is not possible in a Check-in Policy
	/// but can be accessed from the server. This has the advantages of simpler installation (server side not client), and crossing both VS and Eclipse clients
	/// Disadvantage - no verification prior to check-in, not essential but could be achieved with check-in polcy to simply validate the work items so that the event handler 
    /// will have valid work items to associate.
	/// 
	/// Needs to be deployed to C:\Program Files\Microsoft Team Foundation Server 2010\Application Tier\Web Services\bin\Plugins
	/// </summary>
	public class EmbeddedWorkItemEventHandler : ISubscriber
	{
		const string EVENT_NAME = "EmbeddedWIAssociationEventHandler";
		const string CHANGESET_LINK = "Fixed in Changeset";
		const string APP_LOG = "Application";
		
		public Type[] SubscribedTypes()
		{
			// Subscribe to checkin events, need Microsoft.TeamFoundation.VersionControl.Server
			return new Type[1] { typeof(CheckinNotification) };
		}

		public string Name
		{
			get { return EVENT_NAME; }
		}

		public SubscriberPriority Priority
		{
			get { return SubscriberPriority.Normal; }
		}

		public EventNotificationStatus ProcessEvent(TeamFoundationRequestContext requestContext, NotificationType notificationType, object notificationEventArgs,
													out int statusCode, out string statusMessage, out ExceptionPropertyCollection properties)
		{
			// Create the event source, if it doesn't exist
			if (!System.Diagnostics.EventLog.SourceExists(EVENT_NAME))
			{
				System.Diagnostics.EventLog.CreateEventSource(EVENT_NAME, APP_LOG);
			}
				
			statusCode = 0;
			properties = null;
			statusMessage = String.Empty;
			try
			{
				// Only if it is a Check-in...
				if (notificationType == NotificationType.Notification && notificationEventArgs is CheckinNotification)
				{
				
					CheckinNotification ev = notificationEventArgs as CheckinNotification;

					// Get the Changeset
					Changeset cs = GetChangeset(requestContext, ev);

					// Get any work items from comments
					int[] workItemIds = Logic.getWorkItemIds(cs.Comment);

					// Only continue if there some work items to associate
					if (workItemIds.Length > 0)
					{
						// Get a reference to the work item store
						TeamFoundation.Client.TfsTeamProjectCollection tpc;
						WorkItemStore workItemStore;
						GetWorkItemStore(requestContext, cs, out tpc, out workItemStore);

						// Need the correct linktype
						RegisteredLinkType csLink = workItemStore.RegisteredLinkTypes[CHANGESET_LINK];

						// Get the change set uri
						string csUri = GetChangesetUri(ev, tpc);

						// For each work item in the list add a link to the changes
						for (int i = 0; i < workItemIds.Length; i++)
						{
							try 
							{
								// Get the workitem, link it and save it
                                WorkItem wi = workItemStore.GetWorkItem(workItemIds[i]);
								wi.Links.Add(new ExternalLink(csLink, csUri));
								wi.Save();
								EventLog.WriteEntry(EVENT_NAME, "Work Item " + workItemIds[i] + " linked to Changeset " + ev.Changeset);
							}
							catch (Exception ex)
							{
								EventLog.WriteEntry(EVENT_NAME, "Unable to link work item " + workItemIds[i] + " to Changeset " + ev.Changeset + ": " + ex.Message);
							}
								
						}

					}
					
				}

			}
			catch (Exception ex)
			{
				EventLog.WriteEntry(EVENT_NAME, "Exception handling embedded work items " + ex.Message);
			}
			return EventNotificationStatus.ActionPermitted;
		}

		private static string GetChangesetUri(CheckinNotification ev, TeamFoundation.Client.TfsTeamProjectCollection tpc)
		{
			VersionControlServer vcs = (VersionControlServer)tpc.GetService(typeof(VersionControlServer));
			string csUri = new Uri(vcs.GetChangeset(ev.Changeset).ArtifactUri.AbsoluteUri).ToString();
			return csUri;
		}

		private static void GetWorkItemStore(TeamFoundationRequestContext requestContext, Changeset cs, out TeamFoundation.Client.TfsTeamProjectCollection tpc, out WorkItemStore workItemStore)
		{
			var pc = requestContext.GetService<TeamFoundationLocationService>();
			var uri = pc.GetSelfReferenceUri(requestContext, pc.ServerAccessMapping);
			
			NetworkCredential cred = CredentialCache.DefaultNetworkCredentials;
			ICredentialsProvider TFSProxyCredentials = new TFSCredentialsProvider(cred);
			TfsTeamProjectCollection currentCollection = new TfsTeamProjectCollection(uri, cred);
			IIdentityManagementService identityManagementService = currentCollection.GetService<IIdentityManagementService>();
			Microsoft.TeamFoundation.Framework.Client.TeamFoundationIdentity identity = identityManagementService.ReadIdentity(IdentitySearchFactor.AccountName, cs.Committer, MembershipQuery.None, ReadIdentityOptions.None);
			tpc = new TfsTeamProjectCollection(uri, cred, TFSProxyCredentials, identity.Descriptor);
			workItemStore = tpc.GetService<WorkItemStore>();
		}

		private static Changeset GetChangeset(TeamFoundationRequestContext requestContext, CheckinNotification ev)
		{
			var s = requestContext.GetService<TeamFoundationVersionControlService>();
			TeamFoundationDataReader reader = s.QueryChangeset(requestContext, ev.Changeset, true, false, true);
			Changeset cs = reader.Current<Changeset>();
			return cs;
		}
	}

    public class TFSCredentialsProvider : ICredentialsProvider
    {
        private readonly NetworkCredential credentials;

        public TFSCredentialsProvider(NetworkCredential credentials)
        {
            this.credentials = credentials;
        }

        public ICredentials GetCredentials(Uri uri, ICredentials failedCredentials)
        {
            return this.credentials;
        }

        public void NotifyCredentialsAuthenticated(Uri uri)
        {
        }

    }

}
